/*
 superNode.go
 Created by hupfei on 2019-08-05
*/

package service

import (
	"encoding/json"
	"github.com/pkg/errors"
	"strconv"
	"time"
	"zimuzu_cvn_web_api/config"
	"zimuzu_cvn_web_api/model"
	"zimuzu_cvn_web_api/pkg/drive/redis"
	. "zimuzu_cvn_web_api/pkg/e"
	"zimuzu_cvn_web_api/pkg/errno"
)

type SuperNodeInfo struct {
	Cvnt           int    `json:"cvnt"`
	RemainingCount int    `json:"remainingCount"`
	TotalCount     int    `json:"totalCount"`
	Wechat         string `json:"wechat"`
}

/*
 * @ Author: hupfei
 * @ Description: 超级节点信息
 **/
func SuperInfo() (info SuperNodeInfo, err error) {
	cacheKey := PublicCacheKeyPre + "SuperNodeInfo"
	if redis.Exists(cacheKey) {
		infoStr, err := redis.Get(cacheKey)
		if err != nil {
			Log("err", "redis获取超级节点信息失败", err.Error())
		} else {
			err = json.Unmarshal([]byte(infoStr), &info)
			if err != nil {
				Log("err", "解析 redis 数据失败", err.Error())
			}
		}
	} else {
		//全网超级节点总数（包含已有的和剩余的）
		nodeCount, err := model.SuperNodeCount()
		if err != nil {
			return info, err
		}

		info = SuperNodeInfo{
			TotalCount: nodeCount,
		}
		infoStr, _ := json.Marshal(info)
		_, _ = redis.Set(cacheKey, infoStr, 10*TimeFormatMin)
	}
	return info, err
}

type SuperNodeUserInfo struct {
	InviteCode    int64  `json:"inviteCode"`
	Nickname      string `json:"nickname"`
	NodeCount     int    `json:"nodeCount"`
	NodeMaxCount  int    `json:"nodeMaxCount"`
	HasNewMsg     int    `json:"hasNewMsg"`
	TotalCvnt     string `json:"totalCvnt"`
	YesterdayCvnt string `json:"yesterdayCvnt"`
	VerifyMode    int    `json:"verifyMode"`
}

/*
 * @ Author: hupfei
 * @ Description: 超级节点用户信息
 **/
func SuperUserInfo(uid int64) (info SuperNodeUserInfo, err error) {
	node, err := model.SuperNodeInfoByUid(uid)
	if err != nil {
		return info, err
	}

	//邀请码
	info.InviteCode = uid

	//昵称
	user, err := GetUserInfo(uid)
	if err != nil {
		return info, err
	}
	info.Nickname = user.Nickname

	//旗下挖矿节点数
	info.NodeCount, err = model.GetSuperNodePledgedNodeNum(node.NodeId)
	if err != nil {
		return info, errno.DbSearchError
	}
	info.NodeMaxCount = node.MaxNodeNum

	//昨日总收益(保留两位小数)
	info.YesterdayCvnt, err = model.SuperNodeIncome(node.NodeId, 2)
	if err != nil {
		return info, err
	}
	info.YesterdayCvnt = FormatValue(info.YesterdayCvnt, 2)

	//累计总收益(格式化成 万)
	info.TotalCvnt, err = model.SuperNodeIncome(node.NodeId, 1)
	if err != nil {
		return info, err
	}
	info.TotalCvnt = CvntFormat(info.TotalCvnt)

	//当前审核模式
	info.VerifyMode = node.VerifyMode

	//是否有新的申请信息
	info.HasNewMsg, _ = model.GetApplyNum(uid)

	return info, err
}

/*
 * @ Author: hupfei
 * @ Description: 移除旷工
 **/
func RemoveMiner(uid, minerUid int64, canRemove CheckCanDeleteMinerRes) error {
	var cvnt float64
	if canRemove.JoinTimeCondition == 0 || canRemove.AverageIncomeCondition == 0 || canRemove.NodeNumCondition == 0 {
		//不满足删除条件需要支付cvnt
		cvnt = float64(20 * canRemove.MinerNodeNum)
	}

	superNode, err := model.SuperNodeInfoByUid(uid)
	if err != nil {
		return errno.DbSearchError
	}

	//1.释放该矿工所有令牌
	//2.新增super_node_unbind_log日志
	//3.更新super_node_miner中status=30,super_node_id=0
	//4.超级结点 cvn_log日志、支付违约金
	//5.矿工 cvn_log日志、收到违约金

	//开启事务
	tx, err := config.C.DB.DB.Beginx()
	if err != nil {
		Log("err", "RemoveMiner 事务开启失败：", err.Error())
		return errno.DbError
	}

	//1.释放该矿工所有令牌
	ok := model.ReleaseAllToken(tx, minerUid)
	if !ok {
		_ = tx.Rollback()
		return errno.DbError
	}

	//2.新增super_node_unbind_log日志
	res, err := tx.Exec(`INSERT INTO super_node_unbind_log (node_id, miner_id, type, create_time) VALUES (?, ?, ?, ?)`, superNode.NodeId, minerUid, 10, time.Now().Unix())
	if err != nil {
		Log("err", "RemoveMiner 新增super_node_unbind_log失败：", err.Error())
		_ = tx.Rollback()
		return errno.DbError
	}
	id, err := res.LastInsertId()
	if err != nil {
		Log("err", "RemoveMiner 新增super_node_unbind_log后获取id失败：", err.Error())
		_ = tx.Rollback()
		return errno.DbError
	}
	if id <= 0 {
		Log("err", "RemoveMiner 新增super_node_unbind_log的id异常,id=：", id)
		_ = tx.Rollback()
		return errno.DbError
	}

	//3.更新super_node_miner
	res, err = tx.Exec(`update super_node_miner set status = 30, super_node_id = 0 where uid = ?`, minerUid)
	if err != nil {
		Log("err", "RemoveMiner 更新super_node_miner失败：", err.Error())
		_ = tx.Rollback()
		return errno.DbError
	}
	row, err := res.RowsAffected()
	if err != nil {
		Log("err", "RemoveMiner 获取更新super_node_miner影响的行数失败：", err.Error())
		_ = tx.Rollback()
		return errno.DbError
	}
	if row != 1 {
		Log("err", "RemoveMiner 更新mg_token影响行数为：", row)
		_ = tx.Rollback()
		return errno.DbError
	}

	if cvnt > 0 {
		//超级结点 cvn_log日志、支付违约金
		updateAmountData := UpdateCvnAmountParams{
			Uid:    uid,
			Amount: -cvnt,
			Source: model.LogSourceSuperNodeExitForSuperNode,
		}
		_, err = UpdateCvnAmountBalance(tx, updateAmountData)
		if err != nil {
			Log("err", "RemoveMiner 更新超级结点 cvn_log日志、支付违约失败：", err.Error())
			tx.Rollback()
			return errno.DbError
		}

		//5.矿工 cvn_log日志、收到违约金
		updateMinerAmountData := UpdateCvnAmountParams{
			Uid:    minerUid,
			Amount: cvnt,
			Source: model.LogSourceSuperNodeExitForMiner,
		}
		_, err = UpdateCvnAmountBalance(tx, updateMinerAmountData)
		if err != nil {
			Log("err", "RemoveMiner 更新矿工 cvn_log日志、收到违约失败：", err.Error())
			tx.Rollback()
			return errno.DbError
		}
	}

	err = tx.Commit()
	if err != nil {
		Log("err", "RemoveMiner 提交事务失败：", err.Error())
		_ = tx.Rollback()
		return errno.DbError
	}

	return nil
}

/*
 * @ Author: hupfei
 * @ Description: 旷工贡献列表
 **/
func IncomeList(uid int64, page, per int) (info []model.IncomeInfo, err error) {
	//获取超级节点信息
	node, err := model.SuperNodeInfoByUid(uid)
	if err != nil {
		return nil, errno.DbSearchError
	}

	userBindList, err := model.GetSuperNodeAllUserNodeNum(node.NodeId, 0)
	if err != nil {
		return nil, errno.DbSearchError
	}

	userPledgedList, err := model.GetSuperNodeAllUserNodeNum(node.NodeId, 1)
	if err != nil {
		return nil, errno.DbSearchError
	}

	info, err = model.GetMinerInComeList(node.NodeId, page, per)
	if err != nil {
		return nil, err
	}

	for k, miner := range info {
		//totalCvnt
		cvnt, _ := strconv.ParseFloat(miner.TotalCvnt, 64)
		info[k].TotalCvnt = CvntFormat(cvnt)

		//yesterdayCvnt
		cvnt, _ = strconv.ParseFloat(miner.YesterdayCvnt, 64)
		info[k].YesterdayCvnt = CvntFormat(cvnt)

		for _, v := range userBindList {
			if v.Uid == miner.Uid {
				info[k].NodeNum = v.PcNum + v.LinuxNum + v.RouterNum

				break
			}
		}

		for _, v := range userPledgedList {
			if v.Uid == miner.Uid {
				info[k].PcNum = v.PcNum
				info[k].LinuxNum = v.LinuxNum
				info[k].RouterNum = v.RouterNum
				info[k].MiningNodeNum = v.PcNum + v.LinuxNum + v.RouterNum

				break
			}
		}
	}

	return
}

/*
 * @ Author: hupfei
 * @ Description: 收益明细列表
 **/
func IncomeDetailList(uid int64, page, per int) (info []model.SuperNodePayment, err error) {
	node, err := model.SuperNodeInfoByUid(uid)
	if err != nil {
		return nil, err
	}

	info, err = model.GetSuperNodePaymentList(node.NodeId, page, per)
	if err != nil {
		return nil, err
	}

	return info, nil
}

/*
 * @ Author: hupfei
 * @ Description: 填写超级节点邀请码
 **/
func SuperInviteCode(uid int64, superNodeUid int64, minerNodeNum int) error {
	//是否已经申请过
	hasApplied := model.CheckMinerHasApplied(uid, superNodeUid)
	if hasApplied {
		return errors.New("您已经申请过加入该超级节点，无需再次申请")
	}

	miner, err := model.GetMinerInfoByUid(uid)
	if err != nil {
		return errno.DbSearchError
	}

	superNode, err := model.SuperNodeInfoByUid(superNodeUid)
	if err != nil {
		return errno.DbSearchError
	}

	if superNode.NodeId <= 0 {
		return errno.ErrInviteCode
	}

	//该矿工质押的令牌数量
	pledgedNum, err := model.GetUserPledgedTokenNodeNumExceptRouter(uid)
	if pledgedNum > minerNodeNum {
		return errors.New("您申请的节点数不能少于您已经质押的节点数")
	}

	//判断该超级节点是否有用(状态: 10 : 启用中 20：禁用中)
	if superNode.Status == 20 {
		return errors.New("该超级节点已被禁用")
	}

	if superNode.NodeId == miner.SuperNodeId {
		return errors.New("您已经是该超级节点旗下旷工")
	}

	if miner.Status > 0 && miner.Status != model.MinerStatusUnbindBySuperNode && miner.Status != model.MinerStatusUnbindBySelf {
		return errors.New("请先解除与当前超级节点的绑定关系")
	}

	//该超级节点是否禁止任何人加入
	if superNode.VerifyMode == model.VerifyModeForbid {
		return errors.New("此超级节点设置为禁止任何矿工加入，请更换其他超级节点邀请码加入")
	}

	//该超级节点旗下的节点数量
	pledgedNodeNum, err := model.GetSuperNodePledgedNodeNum(superNode.NodeId)
	if err != nil {
		return errno.DbSearchError
	}

	if pledgedNodeNum == superNode.MaxNodeNum {
		return errors.New("该超级节点挖矿名额已满")
	}

	if minerNodeNum+pledgedNodeNum > superNode.MaxNodeNum {
		return errors.New("申请的挖矿名额不得大于该超级节点的剩余名额")
	}

	//super_node_apply中新增申请记录
	err = model.InsertSuperNodeApplyInfo(uid, superNodeUid, minerNodeNum)
	if err != nil {
		return err
	}

	return nil
}

//质押令牌前的操作
func StartPledgeToken(minerUid int64) (superNodeId int64, err error) {
	//查询矿工信息
	miner, err := model.GetMinerInfoByUid(minerUid)
	if err != nil {
		return 0, errno.DbSearchError
	}

	//该用户是否填写过超级节点邀请码
	minerExist := model.CheckMinerExist(minerUid)

	if !minerExist {
		//用户不是矿工
		return 0, errors.New("您还没有填写过超级节点邀请码")
	}

	if miner.Status == model.MinerStatusUnbindBySuperNode {
		return 0, errors.New("您已被超级节点踢出，请重新填写超级节点邀请码")
	}

	if miner.Status == model.MinerStatusUnbindBySelf {
		return 0, errors.New("请重新填写超级节点邀请码")
	}

	//判断该超级节点是否有用
	superNode, err := model.SuperNodeInfoByNodeId(miner.SuperNodeId)
	if err != nil {
		return 0, errno.DbSearchError
	}
	if superNode.Status == 20 {
		//10:启用中 20:禁用中
		return 0, errors.New("您填写的超级节点邀请码已被禁用")
	}

	return miner.SuperNodeId, nil
}

//查询矿工信息
//Auth:Xu
func GetMinerInfo(uid int64) (info model.MinerInfo, err error) {
	info, err = model.GetMinerInfoByUid(uid)
	if err != nil {
		err = errors.New("查询矿工信息失败")
		return
	}
	return
}

//通过、拒绝(1 通过  2 拒绝)
func ApplyResponse(uid int64, minerUid int64, source int) (err error) {
	applyInfo, err := model.GetMinerApplyInfo(minerUid, uid)
	if err != nil {
		return err
	}

	//该申请是否已经过期
	if applyInfo.Status == model.MinerApplyStatusExpired {
		return errors.New("该申请已过期")
	}

	if source == 1 {
		//通过
		minerExist := model.CheckMinerExist(minerUid)
		err = model.AgreeMinerApply(minerUid, uid, applyInfo.NodeNum, minerExist)
	} else {
		//拒绝
		err = model.UpdateMinerApplyStatus(minerUid, uid, model.MinerApplyStatusRefused)
	}

	return err
}

type CheckCanDeleteMinerRes struct {
	JoinTimeCondition      int `json:"joinTimeCondition"` //0-不满足，1-满足
	AverageIncomeCondition int `json:"averageIncomeCondition"`
	NodeNumCondition       int `json:"nodeNumCondition"`
	MinerNodeNum           int `json:"minerNodeNum"` //该矿工质押的节点数
}

//检查是否满足删除条件
/*
1.矿工已加入节点超过7天；
2.矿工的单节点7日平均收益低于超级节点旗下单节点的7日平均收益；
3.超级节点旗下节点数超过900；
(强制删除需支付（20CVNT*n个节点）)
*/
func CheckCanDeleteMiner(uid, minerUid int64) (res CheckCanDeleteMinerRes, err error) {
	if uid == minerUid {
		//删除自己
		res.JoinTimeCondition = 1
		res.AverageIncomeCondition = 1
		res.NodeNumCondition = 1
		return res, nil
	}

	removeMinerOn, err := model.GetGlobalVar(model.GlobalNameRemoveMinerOn)
	if err != nil {
		return res, errno.DbSearchError
	}
	removeMinerOnInt, _ := strconv.ParseInt(removeMinerOn, 10, 64)
	if removeMinerOnInt == 0 {
		res.JoinTimeCondition = 1
		res.AverageIncomeCondition = 1
		res.NodeNumCondition = 1

		return res, nil
	}

	//查询矿工信息
	miner, err := model.GetMinerInfoByUid(minerUid)
	if err != nil {
		return res, errno.DbSearchError
	}
	if miner.JoinTime+7*24*60*60 < int(time.Now().Unix()) {
		res.JoinTimeCondition = 1
	}

	superNode, err := model.SuperNodeInfoByUid(uid)
	if err != nil {
		return res, errno.DbSearchError
	}
	//旗下挖矿节点数
	nodeCount, err := model.GetSuperNodePledgedNodeNum(superNode.NodeId)
	if err != nil {
		return res, errno.DbSearchError
	}
	if nodeCount > 900 {
		res.NodeNumCondition = 1
	}

	//收益条件
	minerAverageIncome, err := model.GetSuperNodeAverageIncome(superNode.NodeId)
	if err != nil {
		return res, errno.DbSearchError
	}
	if miner.IncomeAbility < minerAverageIncome {
		res.AverageIncomeCondition = 1
	}

	if res.JoinTimeCondition == 0 || res.AverageIncomeCondition == 0 || res.NodeNumCondition == 0 {
		//不满足任意一条
		nodeNum, err := model.GetUserPledgedNodeNum(minerUid)
		if err != nil {
			return res, errno.DbSearchError
		}
		res.MinerNodeNum = nodeNum.PcNum + nodeNum.LinuxNum + nodeNum.RouterNum
	}

	return res, nil
}

//获取超级节点挖矿剩余名额（不包含路由器）
func GetSuperNodeRemainingNodeNumByMinerUid(minerUid int64) (num int, err error) {
	//查询矿工信息
	miner, err := model.GetMinerInfoByUid(minerUid)
	if err != nil {
		return num, errno.DbSearchError
	}

	//旗下挖矿节点数
	num, err = model.GetSuperNodePledgedNodeNum(miner.SuperNodeId)
	if err != nil {
		return num, errno.DbSearchError
	}

	superNode, err := model.SuperNodeInfoByNodeId(miner.SuperNodeId)
	if err != nil {
		return num, errno.DbSearchError
	}

	return superNode.MaxNodeNum - num, nil
}

//获取矿工挖矿剩余名额（不包含路由器）
func GetMinerRemainingNodeNumByUid(uid int64) (num int, err error) {
	//查询矿工信息
	miner, err := model.GetMinerInfoByUid(uid)
	if err != nil {
		return num, errno.DbSearchError
	}

	nodeNum, err := model.GetUserPledgedTokenNodeNumExceptRouter(uid)
	if err != nil {
		return num, errno.DbSearchError
	}

	return miner.MaxNodeNum - nodeNum, nil
}

type CheckSecedeData struct {
	VerifyOn    int     `json:"verifyOn"`
	Check       int     `json:"check"`
	Conditions  []int   `json:"conditions"`
	NeedPay     float64 `json:"needPay"`
	MineCvnt    float64 `json:"mineCvnt"`
	Ratio       float64 `json:"ratio"`
	superNodeId int64   `json:"superNodeId"`
}

//检查是否满足退出条件
//Author: TXG
func CheckSecede(uid int64) (data CheckSecedeData, err error) {
	var (
		n    int
		t    = 86400 * 15
		rate = 0.05
	)
	data.Conditions = []int{0, 0, 0}
	verifyOn, err := model.GetGlobalVarValue(config.C.DB.DB, 33)
	if err != nil {
		err = errno.GetDataErr
		return data, err
	}
	if verifyOn == "0" {
		data.VerifyOn = 0
		return data, nil
	}
	data.VerifyOn = 1
	minerInfo, err := model.GetMinerInfoByUid(uid)
	if err != nil {
		err = errno.GetDataErr
		return data, err
	}
	if minerInfo.SuperNodeId == 0 {
		err = errno.IllegalErr
		return data, err
	}
	if minerInfo.Status != 20 {
		err = errno.IllegalErr
		return data, err
	}
	data.superNodeId = minerInfo.SuperNodeId
	nowTime := time.Now().Unix()
	if int(nowTime)-minerInfo.JoinTime >= t {
		data.Conditions[0] = 1
	}
	//获取超级节点剩余名额
	superNodeAvailableNum, err := GetSuperNodeRemainingNodeNumByMinerUid(uid)
	if err != nil {
		err = errno.GetDataErr
		return data, err
	}
	//获取矿工已占用名额
	mineUsedQuota, err := model.GetMineUsedQuota(uid)
	if err != nil {
		err = errno.GetDataErr
		return data, err
	}
	//会占用名额未挖矿节点数
	unmineNodeNum, err := model.GetUnmineNodeNum(uid)
	if err != nil {
		err = errno.GetDataErr
		return data, err
	}
	if mineUsedQuota+unmineNodeNum > minerInfo.MaxNodeNum || unmineNodeNum > superNodeAvailableNum {
		data.Conditions[1] = 1
	}
	//获取超级节点矿工7日平均收益
	superNodeAverageIncome, err := model.GetSuperNodeAverageIncome(minerInfo.SuperNodeId)
	if err != nil {
		err = errno.GetDataErr
		return data, err
	}
	if minerInfo.IncomeAbility < superNodeAverageIncome {
		data.Conditions[2] = 1
	}
	for _, value := range data.Conditions {
		n += value
	}
	if n == len(data.Conditions) {
		data.Check = 1
		return data, nil
	}
	//获取矿工收入
	data.MineCvnt, err = model.GetMineCvnInSuperNode(uid, minerInfo.SuperNodeId, minerInfo.JoinTime)
	if err != nil {
		err = errno.GetDataErr
		return data, err
	}
	data.Ratio = rate
	data.NeedPay = Decimal(data.MineCvnt*data.Ratio, 8)
	if data.NeedPay < 0 {
		err = errno.UserInfoErr
		return data, err
	}
	return data, nil
}

func Secede(uid int64, postData model.SecedeSuperNode) (err error) {
	//检查是否绑定手机号
	userMobile, err := model.GetUserMobile(config.C.DB.DB, uid)
	if err != nil {
		err = errno.GetDataErr
		return err
	}
	if userMobile.Area == 0 || userMobile.Mobile == "" {
		err = errno.NotBindMobile
		return err
	}
	//校验短信验证码
	if errValid := ValidateCode(userMobile.Mobile, SendCodeSceneSecedeSuperNode, postData.Code); errValid != nil {
		err = errValid
		return err
	}
	//检查支付密码是否校验成功
	if ok := IsPayPasswordChecked(uid, postData.U); !ok {
		err = errno.PayPasswordCheckExpire
		return err
	}
	DeleteU(uid, postData.U)
	//检查是否满足退出条件
	data, err := CheckSecede(uid)
	if err != nil {
		return err
	}
	//满足条件-直接退出
	if data.Check == 1 {
		err = model.MinerSecede(uid)
		if err != nil {
			err = errno.UpdateDataErr
			return err
		}
		return nil
	}
	if data.NeedPay == 0 {
		err = model.MinerSecede(uid)
		if err != nil {
			err = errno.UpdateDataErr
			return err
		}
		return nil
	}
	//不满足条件-强制退出
	cvbAmount, err := GetUserCvnAmountByUid(uid)
	if data.NeedPay >= cvbAmount.Balance {
		err = errno.WalletNotEnoughERR
		return err
	}
	superNodeInfo, err := model.SuperNodeInfoByNodeId(data.superNodeId)
	if err != nil {
		err = errno.GetDataErr
		return err
	}
	ok, err := MinerForceSecede(uid, superNodeInfo.Uid, data.NeedPay)
	if err != nil || !ok {
		err = errno.UpdateDataErr
		return err
	}
	return nil
}

//强制退出超级节点
func MinerForceSecede(uid int64, superNodeUid int64, needPay float64) (bool, error) {
	var (
		db = config.C.DB.DB
	)
	//开启事务
	tx, err := db.Beginx()
	if err != nil {
		Log("info", err)
		return false, err
	}
	//super_node_miner
	result, err := tx.Exec("UPDATE super_node_miner SET super_node_id = 0, status = 40, max_node_num = 0 WHERE uid = ?", uid)
	if err != nil {
		Log("info", err)
		_ = tx.Rollback()
		return false, err
	}
	aff, err := result.RowsAffected()
	if err != nil {
		Log("info", err)
		_ = tx.Rollback()
		return false, err
	}
	if aff != 1 {
		Log("info", "影响行数为:", aff)
		_ = tx.Rollback()
		return false, err
	}
	//矿工user_amount
	updateAmountData := UpdateCvnAmountParams{
		Uid:    uid,
		Amount: -needPay,
		Source: model.LogSourceSuperNodeExitForMiner,
	}
	_, err = UpdateCvnAmountBalance(tx, updateAmountData)
	if err != nil {
		tx.Rollback()
		return false, err
	}
	//超级节点user_amount
	updateSuperNodeAmountData := UpdateCvnAmountParams{
		Uid:    superNodeUid,
		Amount: needPay,
		Source: model.LogSourceSuperNodeExitForSuperNode,
	}
	_, err = UpdateCvnAmountBalance(tx, updateSuperNodeAmountData)
	if err != nil {
		tx.Rollback()
		return false, err
	}

	_ = tx.Commit()
	Log("info", "数据库写入成功")
	return true, nil
}
